背景

开发中的内网项目,近期要进入用户测试阶段了,为了方便用户提交bug,领导让我用 Nextcloud + OnlyOffice Document Server + OnlyOffice插件 搭建一个内网多人协作文档系统 (其实私有化部署个禅道之类的系统肯定比多人excel文档更好)

经过一番研究,梳理出了三者间关系。Nextcloud即个人网盘,OnlyOffice插件实现在网页中编辑网盘中的文件,OnlyOffice Document Server实现多人协作编辑文件。

全文假设安装机子的ip为10.96.38.201,需要根据实际情况修改

安装与使用

前置环境需求

能装最新的docker以及docker compose就装最新的,至少也要如下版本

1
2
3
4
docker -v
Docker version 24.0.7, build afdd53b
docker-compose -v
docker-compose version 1.29.1, build c34c88b2

安装Nextcloud以及OnlyOffice Document Server

推荐使用docker-compose统一安装,使用镜像nextcloud:stable186184848/documentserver,以及数据库mariadb:10.11.13,数据库推荐挂载卷到宿主机以实现数据持久化保存。

186184848/documentserver镜像为社区版源码编译 移除人数限制 移动端编辑 多核心 中文字体,推荐使用

创建docker-compose.yml文件,cd到文件所在目录执行docker-compose up -d,查看容器运行状态。若启动成功,访问http://10.96.38.201:9997应该要看到ONLYOFFICE文档页面,访问http://10.96.38.201:9995应该要看到nextcloud管理员配置页面。

docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
version: '3'

services:
db:
image: mariadb:10.11.13
container_name: nextcloud-db
restart: always
cap_add:
- SYS_TIME
security_opt:
- seccomp=unconfined
environment:
- MYSQL_ROOT_PASSWORD=YOURPWD
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=YOURPWD
volumes:
- /home/nextcloud/db:/var/lib/mysql
networks:
- office-net

nextcloud:
image: nextcloud:stable
container_name: nextcloud
restart: always
depends_on:
- db
ports:
- "9995:80"
environment:
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=YOURPWD
- MYSQL_HOST=db
volumes:
- nextcloud-data:/var/www/html
networks:
- office-net

onlyoffice:
image: 186184848/documentserver
container_name: onlyoffice
restart: always
ports:
- "9997:80"
- "9998:8000"
environment:
- JWT_ENABLED=false
networks:
- office-net
security_opt:
- seccomp=unconfined

volumes:
nextcloud-data:
db-data:

networks:
office-net:

安装配置Nextcloud OnlyOffice插件

访问 https://apps.nextcloud.com/apps/onlyoffice 根据安装的nextcloud版本下载对应的插件版本并解压。

在宿主机中执行docker cp ./onlyoffice nextcloud:/var/www/html/apps把解压好的插件拷贝到nextcloud插件文件夹中

再执行docker exec -u www-data nextcloud php occ app:enable onlyoffice启用插件

浏览器打开 Nextcloud → 管理 → ONLYOFFICE

按如下配置填写:

1
2
3
4
5
6
7
8
9
# 使用服务名:
ONLYOFFICE Docs地址:http://onlyoffice/
服务器内部请求 ONLYOFFICE Docs 的地址:http://onlyoffice/
ONLYOFFICE Docs 内部请求服务器的地址:http://nextcloud/

# 若使用服务名的方式存在问题,也可以直接指定IP端口:
ONLYOFFICE Docs地址:http://10.96.38.201:9997/
服务器内部请求 ONLYOFFICE Docs 的地址:http://10.96.38.201:9997/
ONLYOFFICE Docs 内部请求服务器的地址:http://10.96.38.201:9995/

点击保存后看到如下内容即为配置成功,可以传一个文件验证试试,正常应该就可以在线编辑文档了

一些坑

1. 域解析问题

若访问nextcloud有问题,需要查看nextcloud容器内的/var/www/html/config/config.php文件中的trusted_domains配置部分,域名端口要添加上部署机器的ip,比如:

1
2
3
4
'trusted_domains' => 
array (
0 => '10.96.38.201:9995',
),

推荐直接在容器内执行yum install vim安装vim,方便编辑配置文件

若内网机子不方便安装vim,只能拷贝到宿主机编辑后再拷贝回去,需要执行下面几个操作,保证拷贝回容器后的config.php文件属于www-data用户

1
2
3
4
5
docker cp nextcloud:/var/www/html/config/config.php ./config.php
# 编辑 config.php...
docker cp ./config.php nextcloud:/var/www/html/config/config.php
docker exec -u root nextcloud chown www-data:www-data /var/www/html/config/config.php
docker exec -u root nextcloud chmod 640 /var/www/html/config/config.php

2.请求过多问题

运行一段时间后,通过分享链接匿名访问可能会遇到“请求过多 您的网络请求过多。如果出现错误,请稍后重试或与您的管理员联系。”的提示。
这个是nextcloud本身的限流机制,可以在config.php中添加配置关闭限流保护机制:

config.php
1
'auth.bruteforce.protection.enabled' => false,

并且执行docker exec -u www-data nextcloud php occ security:reset-bruteforce-attempts清除缓存

背景

近期因为一些原因,需要频繁切换网络环境,每次切换环境都需要拔插网线或者是从控制面板中禁用网卡。不喜欢这种重复的无意义操作,便寻思可以写个脚本调用方法来切换网卡状态,于是先整了个脚本,但是每次还需要双击运行,而且无法直观地看到两个网卡当前的状态,又想到或许可以用electron构建个app,用js调用api来切换网卡状态,遂跟gpt老师进行了一番探讨,成功写了个小软件实现实时监控网卡状态+一键切换网络环境的功能。

软件样式设计

输入需求让gpt老师帮忙画了个设计图,最终实现效果如下:

代码实现

有了设计图就可以开工了,由于对electron并不了解,让gpt先帮忙搭了个框架。

项目结构

1
2
3
4
5
6
7
8
9
10
network-switch-app/
├── main.js // 主进程:窗口创建、状态检测、指令执行
├── preload.js // 预加载脚本(可用于暴露安全 API)
├── renderer/ // 渲染进程
│ ├── index.html
│ ├── renderer.js
├── assets/
│ └── icon.ico // 应用图标
├── config.json // 网卡名配置文件(intranet/internet)
├── package.json

main.js 中开启程序窗口以及调用系统API,preload.js 中挂载全局方法,在renderer中实现页面内容以及js逻辑处理

主进程 main.js 核心功能

1. 创建窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const win = new BrowserWindow({
width: 280,
height: 160,
useContentSize: true,
frame: false,
alwaysOnTop: true,
resizable: false,
transparent: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
icon: path.join(__dirname, 'assets', 'icon.ico'),
})

2.读取可编辑的配置文件

1
2
const configPath = path.join(path.dirname(app.getPath('exe')), 'config.json')
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))

3. 执行网络切换(Windows)

1
exec(`netsh interface set interface name="${name}" admin=ENABLED`)

4. 获取网卡状态

1
2
3
4
5
6
7
8
9
10
const getAdapterStatus = (name) =>
new Promise((resolve) => {
exec(
`powershell -Command "(Get-NetAdapter -Name \\"${name}\\" -ErrorAction SilentlyContinue).Status"`,
(err, stdout) => {
const status = stdout.trim()
resolve(status === 'Up' ? 'Up' : 'Down')
}
)
})

API串联

1. main.js:主进程注册功能

1
2
3
4
// 网卡状态检查
ipcMain.handle('get-status', async () => {})
// 切换模式
ipcMain.handle('toggle-mode', async () => {})

2. preload.js:暴露 API 的桥梁

1
2
3
4
5
6
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('networkAPI', {
getStatus: async () => await ipcRenderer.invoke('get-status'),
toggleMode: async () => await ipcRenderer.invoke('toggle-mode')
})

3. renderer.js:渲染进程使用 API

1
2
3
async function updateStatus() {
const status = await window.networkAPI.getStatus()
}

打包构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"build": {
"appId": "com.fbz.networkswitch",
"productName": "内外网切换器",
"files": [
"main.js",
"preload.js",
"renderer/**/*",
"config.json"
],
"extraFiles": [
{
"from": "config.json",
"to": "config.json"
}
],
"directories": {
"buildResources": "assets",
"output": "dist"
},
"win": {
"target": "nsis",
"icon": "assets/icon.ico",
"sign": false
},
"nsis": {
"oneClick": false,
"perMachine": true
}
}

注意事项:

结语

之前也有想研究electron,但是一直没什么契机,这次刚好遇到这种需求,便借着chatgpt写了个小工具,也算是对其有了初步的认知,使用electron前端开发也能快速上手开发程序了。
待下一次有新想法的时候,再深入研究研究

背景

前几年搭建了博客,但是一直都没有投入编写内容,最近又捡起来重新折腾了一下,重新梳理了一下hexo的使用,加深了理解,在此记录一下做个备忘。

一、安装与使用

1. 安装 Hexo CLI

1
npm install -g hexo-cli

2. 初始化博客项目

1
2
3
hexo init blog
cd blog
npm install

3. 安装并启用主题

个人喜欢Next主题的样式,因此这里选用Next主题

1
2
3
4
# 使用npm安装主题
npm install hexo-theme-next
# 拷贝配置文件到根目录
cp node_modules/hexo-theme-next/_config.yml _config.next.yml

修改_config.yml中的theme,以启用next主题

Hexo config file
1
theme: next

可以在_config.next.yml中修改scheme以切换系统样式,个人喜欢gemini的设计

Next config file
1
scheme: Gemini

4. 创建文章

1
hexo new "文章标题"

生成文件位于:source/_posts/文章标题.md

5. 本地预览

1
hexo server

访问:http://localhost:4000 进行本地预览

二、推荐插件

1. hexo-generator-sitemap

部署/运行时会生成sitemap

执行npm i hexo-generator-sitemap安装

并在配置文件_config.yml中添加以下配置,重新编译运行并访问http://localhost:4000/sitemap.xml即可看到网站地图的输出了

Hexo config file
1
2
sitemap:
path: sitemap.xml

2. hexo-generator-searchdb

开启本地搜索功能

执行npm i hexo-generator-searchdb安装

并在配置文件_config.yml中添加以下配置,重新编译运行后点击菜单栏的“搜索”按钮,即可搜索站内博客

Hexo config file
1
2
search:
enable: true

三、部署与SEO优化

部署方案

一开始采用GitHub托管,有提交自动触发构建并部署,然后域名解析到GitHub部署页面的方案。但是由于gfw,墙内访问比较困难(公司破网挂不挂梯子都很难打开自己的博客),且添加评论时,会遇到跨域问题不好解决。遂改为在服务器上打包构建,用nginx部署。

在服务器上拉取仓库并执行npm i安装依赖,在仓库根目录创建部署脚本deploy.sh

deploy.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

echo "🔄 拉取最新代码..."
cd /home/webapps/blog
git pull origin main

echo "📦 安装依赖..."
npm install

echo "🧹 清理旧生成文件..."
npx hexo clean

echo "📝 生成静态文件..."
npx hexo generate

输入bash /path/to/deploy.sh即可执行部署脚本自动打包构建。

证书申请

配置nginx之前,还需要注册一下证书以开启https协议。

首先确保/etc/ssl/certs/etc/ssl/private目录存在,若不存在,执行以下命令创建目录并赋予权限

1
2
3
4
5
mkdir -p /etc/ssl/private
chmod 700 /etc/ssl/private

mkdir -p /etc/ssl/certs
chmod 700 /etc/ssl/certs

执行以下命令签发证书,指定letsencrypt服务可以不用注册邮箱,且支持自动续期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 先停止nginx服务
bt stop nginx

# 签发证书
acme.sh --issue --standalone -d your.domain.com --ecc --server letsencrypt

# 安装证书,最后的reloadcmd修改为本机重载ng配置的指令,我用宝塔面板安装的,因此执行bt reload nginx
acme.sh --install-cert -d your.domain.com --ecc \
--key-file /etc/ssl/private/your.domain.com.key \
--fullchain-file /etc/ssl/certs/your.domain.com_bundle.crt \
--reloadcmd "bt reload nginx"

# 重载nginx
bt reload nginx

nginx配置

接下来在nginx配置中添加以下配置,再重载即可访问部署后的博客页面了

nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
listen 80;
server_name your.domain.com;

# 强制跳转到 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name your.domain.com;

# SSL 证书路径(你需要替换成自己的)
ssl_certificate /etc/ssl/certs/your.domain.com_bundle.crt;
ssl_certificate_key /etc/ssl/private/your.domain.com.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

root /path/to/博客仓库/public;
index index.html;

access_log /www/wwwlogs/access_blog.log;

location / {
try_files $uri $uri/ /index.html;
}
}

SEO优化

SEO优化比较复杂,只是记录下自己做过的操作,还在研究学习中。

以谷歌搜索为例,访问地址:https://search.google.com/search-console,输入要索引的域名,谷歌会返回一个校验码文件。创建一个静态文件目录source/static存放这个文件。

并在hexo配置文件中添加跳过渲染内容,避免部署后访问校验文件时出现内容嵌套在博客框架中,导致校验不通过的问题。重新打包并访问https://your.domain.com/googledxxxxxxxxxx应该能看到输出google-site-verification: googledxxxxxxxxxx.html

_config.yml
1
2
skip_render:
- static/**/*

站点验证成功后还需要添加一下sitemap,这样谷歌可以知道需要抓取的子页面有哪些。若安装了上文的sitemap插件,可在ng添加如下配置,访问https://your.domain.com/sitemap时即可直接重定向到https://your.domain.com/sitemap.xml

1
2
3
4
5
6
7
8
...
location / {
try_files $uri $uri/ /index.html;
}

location = /sitemap {
return 301 /sitemap.xml;
}

然后在谷歌控制台中找到并打开“站点地图”菜单栏,在“添加新的站点地图”中输入sitemap,点击提交即可。

四、开启站内评论

这里选用twikoo作为站内评论组件 (需要next主题8.x版本支持)

先在服务器上启动服务,访问ip:port若看到输出Twikoo 云函数运行正常,请参考 https://twikoo.js.org/frontend.html 完成前端的配置 则为启动成功了

1
2
3
4
5
6
7
8
docker run -d \
--name twikoo \
-e TZ=Asia/Shanghai \
-e TWIKOO_THROTTLE=10 \
-v /home/twikoo/data:/app/data \
-p 8080:8080 \
--restart=always \
imaegoo/twikoo

为了避免跨域,这里用nginx把twitoo代理转发到跟博客同域名下,在nginx博客配置部分添加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
...
location / {
try_files $uri $uri/ /index.html;
}

# 👇 添加 Twikoo 的反向代理配置
location /twikoo/ {
proxy_pass http://127.0.0.1:8080/; # 修改为twikoo服务的端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

然后在博客根目录执行npm i hexo-next-twikoo安装twikoo组件,在next配置文件中添加如下配置,重新打包部署后访问博客页面应该就能看到站内评论组件了

_config.next.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Multiple Comment System Support
comments:
# Available values: tabs | buttons
style: tabs
# Choose a comment system to be displayed by default.
# Available values: disqus | disqusjs | changyan | livere | gitalk | utterances
active: disqus
# Setting `true` means remembering the comment system selected by the visitor.
storage: true
# Lazyload all comment systems.
lazyload: false
# Modify texts or order for any naves, here are some examples.
nav:
twikoo:
order: -1
text: Twikoo 评论
disqus:
text: Load Disqus
order: -1
#gitalk:
# order: -2

twikoo:
enable: true
envId: https://your.domain.com/twikoo # 或 http://ip:port,改成你自己的地址
region: ''
path: window.location.pathname
visitor: true
commentCount: true

五、一些问题

1. 图片引入问题

配置文件中设置post_asset_folder: true,这样在创建博客时会生成博客同名的文件夹,便于管理图片,一开始尝试markdown原生的图片引入写法![图片alt](图片链接 "图片title")最终渲染出来的图片路径始终不对,一番研究发现用{% asset_img 图片文件名 描述文字 %}这个标签才能成功渲染出实际路径

背景

最近在做的项目有老年人模式的需求——也就是动态切换字体大小、元素间距等。

解决方案

项目使用element-uisasssass-loader,在scss文件中定义变量,并利用:export导出变量,供JS使用。JS中读取变量,使用setProperty方法设置全局css变量。项目中所有需要动态改变大小的,都不直接使用px,而是使用css变量进行设置。

使用开关动态切换常规/老年人模式变量,以切换全局css变量值。

以下是代码的实现:

目录结构

1
2
3
4
5
6
src
├── styles
│ ├── index.scss
│ ├── variables.scss
│ └── variables-senior.scss
└── App.vue

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* variables.scss */

/* 无需动态改变的常量 */
$main_color: #4077f4;
// ...

/* 差异化变量 */
$variables: (
// 字号
font_size_extra_large: 20px,
font_size_large: 18px,
font_size_medium: 16px,
font_size_base: 14px,
font_size_small: 13px,
font_size_extra_small: 12px,
// checkbox
checkbox_after_left: 4px,
checkbox_after_top: 1px,
// 表单项
form_item_margin_bottom: 16px,
search_btn_width: 70px,
search_btn_detail_width: 60px
);
:export {
/* 常量部分 */
main_color: $main_color;
// ...

/* 变量自动导出 */
@each $name, $value in $variables {
#{$name}: #{$value};
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* variables-senior.scss */

// 字体增量,可以统一控制放大倍率
$font_size_increment: 5px;

/* 差异化变量 */
$variables: (
// 字号
font_size_extra_large: 20px + $font_size_increment,
font_size_large: 18px + $font_size_increment,
font_size_medium: 16px + $font_size_increment,
font_size_base: 14px + $font_size_increment,
font_size_small: 13px + $font_size_increment,
font_size_extra_small: 12px + $font_size_increment,
// checkbox
checkbox_after_left: 6px,
checkbox_after_top: 3px,
// 表单项
form_item_margin_bottom: 24px,
search_btn_width: 80px,
search_btn_detail_width: 80px
);
:export {
/* 变量自动导出 */
@each $name, $value in $variables {
#{$name}: #{$value};
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- 有的元素不能仅用变量控制,这时候就需要使用class .senior-mode,调整元素在老年人模式下的表现 -->
<template>
<div id="app" :class="{ 'senior-mode': isSeniorMode }">
<el-switch v-model="isSeniorMode" @change="toggleMode">
老年人模式
</el-switch>
<router-view />
</div>
</template>
<script>
import normalVariables from "@/styles/variables.scss"
import seniorVariables from "@/styles/variables-senior.scss"
export default {
name: "app",
data() {
return {
isSeniorMode: false,
}
},
mounted() {
this.setMode()
},
methods: {
toggleMode() {
this.setMode(this.isSeniorMode)
},
setMode(isSenior = false) {
const vars = isSenior ? seniorVariables : normalVariables
for (const [key, value] of Object.entries(vars)) {
document.documentElement.style.setProperty(`--${key}`, value)
}
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
/* style/index.scss */
.el-button {
font-size: var(--font_size_base);
&--mini {
font-size: var(--font_size_extra_small);
}
&--small {
font-size: var(--font_size_base);
}
&--medium {
font-size: var(--font_size_medium);
}
}

结语

这个方案的效果还是比较满意的,衍生出来还可以封装成组件,实现大中小三种字号切换的模式

背景

最近在做一个项目,需要在弹窗里展示政策PDF内容。本来想着直接把PDF文件放在前端项目里,但一个文件大几十M,加载起来很慢。而且客户不想看到PDF在网页中的预览框。
考虑到只是展示内容,不需要交互功能,就想着把PDF转成图片,这样既能缩小文件体积,又能提升加载速度。

解决方案

经过一番搜索,发现 ImageMagick 是个不错的选择。它不仅能处理PDF转图片,还支持批量处理,正好符合我的需求。

工具准备

  1. 安装 ImageMagick

    • Windows: 下载安装包或使用 choco install imagemagick
    • 安装后记得重启命令行
  2. 验证安装

    1
    magick --version
  3. 安装ImageMagick底层依赖 Ghostscript

    • 下载适合系统版本的安装包(通常是 gswin64.exe
  4. 验证安装

    1
    gswin64c -version

批量转换脚本

为了方便使用,我写了个批处理脚本,支持批量转换PDF文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@echo off
setlocal enabledelayedexpansion

:: 设置目标 DPI,调整这里即可改变分辨率
set dpi=150

:: 设置输出图片的质量(范围:1-100
set quality=80

:: 定义 A4 的实际尺寸(单位:英寸)
set a4_width_inch=8.27
set a4_height_inch=11.69

:: 使用 PowerShell 动态计算 A4 分辨率(宽度和高度)
for /f "tokens=*" %%A in ('powershell -Command "[math]::Round(%a4_width_inch% * %dpi%)"') do set width=%%A
for /f "tokens=*" %%A in ('powershell -Command "[math]::Round(%a4_height_inch% * %dpi%)"') do set height=%%A

:: 输出宽高以及质量参数以供验证
echo DPI: %dpi%
echo Width: %width% pixels
echo Height: %height% pixels
echo Quality: %quality%%

:: 验证宽高是否正确
if "%width%"=="" (
echo 宽度计算失败,请检查 PowerShell 是否可用或计算逻辑是否正确。
pause
exit /b
)
if "%height%"=="" (
echo 高度计算失败,请检查 PowerShell 是否可用或计算逻辑是否正确。
pause
exit /b
)

:: 遍历当前目录中的所有 PDF 文件
for %%f in (*.pdf) do (
:: 获取文件名(不带扩展名)
set filename=%%~nf
:: 创建与 PDF 同名的输出目录
mkdir "!filename!"
:: 转换 PDF 为图片,确保背景为白色,大小统一,设置质量
magick -density %dpi% "%%f" -background white -alpha remove -alpha off -resize %width%x%height% -gravity center -extent %width%x%height% -quality %quality% "!filename!\page-%%d.webp"
)

echo 转换完成!
pause

使用心得

参数调优经验

  • DPI设置

    • 150 DPI:适合大多数场景,文件大小和质量比较平衡
    • 200 DPI:高清显示,文件稍大
    • 100 DPI:文件最小,但可能不够清晰
  • 质量设置

    • 80-90:推荐设置
    • 70以下:文件小但质量一般
    • 95以上:质量好但文件大

效果对比

转换后的文件结构:

1
2
3
4
5
6
7
项目/
├── convert_pdf_to_images.bat
├── 政策文件.pdf
└── 政策文件/
├── page-0.webp
├── page-1.webp
└── ...

文件大小对比

  • 原PDF:41.6MB
  • 转换后:约5.3M(WebP格式)
  • 压缩率:约87%

总结

这个方案在一定程度上解决了我的问题:

  • ✅ 1M以上的文件体积基本都大幅压缩了
  • ✅ 加载速度明显提升
  • ✅ 保持了良好的显示效果
  • ✅ 支持批量处理,效率高

对于类似的需求,我觉得这个方案还是挺实用的。如果只是展示内容,转成图片确实比直接使用PDF要好很多。


记录一下这个实用的解决方案,以后遇到类似需求可以直接用。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
hexo new "My New Post"

More info: Writing

Run server

1
hexo server

More info: Server

Generate static files

1
hexo generate

More info: Generating

Deploy to remote sites

1
hexo deploy

More info: Deployment

0%